package cn.yj.core.business.service;

import cn.yj.annotation.pagehelper.page.Page;
import cn.yj.api.FileUploadHandler;
import cn.yj.cgs.dba.entity.*;
import cn.yj.cgs.dba.entity.vo.ArticleFormVo;
import cn.yj.cgs.dba.entity.vo.ArticleVo;
import cn.yj.cgs.dba.mapper.ArticleMapper;
import cn.yj.cgs.dba.mapper.ImagesMapper;
import cn.yj.cgs.dba.mapper.UserViewsAgreeMapper;
import cn.yj.common.AppExecutor;
import cn.yj.common.Enums;
import cn.yj.common.ImageUtils;
import cn.yj.common.UUIdUtils;
import cn.yj.common.lock.reentrant.SyncLocalIVariableLock;
import cn.yj.commons.StringPools;
import cn.yj.commons.utils.ServletUtils;
import cn.yj.commons.utils.StringUtils;
import cn.yj.config.SchoolCBDConfig;
import cn.yj.core.business.service.iservice.IArticleService;
import cn.yj.core.business.service.iservice.IMessageService;
import cn.yj.core.kwfilter.KeyWorkFilterService;
import cn.yj.entity.Base64Entity;
import cn.yj.entity.R;
import cn.yj.params.check.annotation.CheckObjectValue;
import cn.yj.params.check.annotation.KeyValue;
import cn.yj.params.check.annotation.Require;
import cn.yj.tools.exception.ServiceException;
import cn.yj.tools.readconfig.PropertiesUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.*;

/**
 * @author 永健
 * @since 2021-04-29 13:39
 */
@Service
public class ArticleService extends ServiceImpl<ArticleMapper, Article> implements IArticleService {

    private static final Logger log = LoggerFactory.getLogger(ArticleService.class);

    @Resource
    private UserViewsAgreeMapper userViewsAgreeMapper;

    @Resource
    private SchoolCBDConfig config;

    @Resource
    private CustomerService customerService;

    @Resource
    private ImagesMapper imagesMapper;

    @Resource
    private IMessageService messageService;

    @Resource
    private IntegralService integralService;

    private static final SyncLocalIVariableLock PUSH_ARTICLE_LOCK = new SyncLocalIVariableLock();

    private static File WATER_FILE;

    static {
        WATER_FILE = new File(PropertiesUtils.getStringValue("school-cbd.water-img-path"));
    }

    private static final ConcurrentHashMap.KeySetView<Object, Boolean> USER_CODE_ARTICLE =
            ConcurrentHashMap.newKeySet();

    /**
     * 记录当天的文章用户
     * 用于判断当天用户是否已经进账一个文章积分
     * 将其在定时任务中配置
     */
    @PostConstruct
    //@Scheduled(cron = "0 0 0 */1 * ?")
    public void initData() {
        USER_CODE_ARTICLE.clear();
        List<String> userCodes = baseMapper.selectListUserCodeByDate(new Date());
        userCodes.forEach(code -> USER_CODE_ARTICLE.add(code));
    }


    @Override
    public Page<ArticleVo> listPage(Map<String, Object> params, Page<ArticleVo> page) {
        Object typeObj = params.get("type");
        Map<String, Object> p = new HashMap<>();
        // 用来计住当前用户是否已经点赞文章的
        p.put("currentUserCode", params.get("currentUserCode"));
        if (StringUtils.isNotNull(typeObj)) {
            String type = typeObj.toString();
            // 官方动态查询
            if ("official".equals(type)) {
                p.put("isAdmin", Enums.Common.ONE);
            } else if ("mine".equals(type)) {
                params.put("status", null);
                if (!StringUtils.isNull(params.get("openid"))) {
                    Customer customer = customerService.getCustomByMiniOpenId(params.get("openid").toString());
                    if (StringUtils.isNull(customer)) {
                        return new Page<>();
                    }
                    p.put("userCode", customer.getUserCode());
                }
            } else if ("sameCity".equals(type)) {
                if (StringUtils.isNull(params.get("city"))) {
                    return new Page<>();
                }
            }
            // 非同城查询，去除所有带来的参数
            if (!"sameCity".equals(type)) {
                params.clear();
            }
            params.putAll(p);
            if ("mine".equals(type)) {
                params.put("status", null);
            } else {
                params.put("status", Enums.Article.STATUS.SUCCESS.name());
            }
        } else {
            params.put("status", Enums.Article.STATUS.SUCCESS.name());
        }
        baseMapper.findList(params, page);
        handlerView(page.getRows());
        return page;
    }

    // 处理浏览事件
    private void handlerView(final List<ArticleVo> articleVos) {
        Object currentUser = ServletUtils.getRequest().getAttribute("currentUser");
        Customer customer = null;
        if (!StringUtils.isNull(currentUser)) {
            customer = (Customer) currentUser;
        }
        Customer finalCustomer = customer;
        AppExecutor.exec(() -> {
            articleVos.forEach(articleVo -> {
                String articleNo = articleVo.getArticleNo();
                long viewCount = articleVo.getViewCount();
                // 检查当前用户是否已经浏览过一次
                if (StringUtils.isNotNull(finalCustomer)) {
                    Integer integer =
                            userViewsAgreeMapper.selectCount(new QueryWrapper<UserViewsAgree>().lambda().eq(UserViewsAgree::getArticleNo, articleNo).eq(UserViewsAgree::getUserCode, finalCustomer.getUserCode()));
                    if (integer <= Enums.Common.ZERO) {
                        ++viewCount;
                        userViewsAgreeMapper.insert(new UserViewsAgree(finalCustomer.getUserCode(), articleNo,
                                Enums.Article.USER_VIEW_TYPE.VIEW.name()));
                    }
                } else {
                    ++viewCount;
                }
                Article article = new Article().setViewCount(viewCount);
                article.setId(articleVo.getId());
                baseMapper.updateById(article);
            });
        });
    }

    @CheckObjectValue(keyValue = @KeyValue(type = ArticleFormVo.class, name = {"type", "isTop", "isAnon", "userCode"}))
    @Override
    @Transactional
    public R pushArticle(ArticleFormVo articleFormVo) {
        Enums.Article.exits(articleFormVo.getType());
        if (StringUtils.isBlank(articleFormVo.getContent()) && articleFormVo.getImages().isEmpty()) {
            throw new ServiceException("请输入内容或者图片");
        }

        // 敏感词过滤
        Set<String> filterWord = KeyWorkFilterService.filter_search_out_word(articleFormVo.getContent(),
                customerService.getOpenIdByUserCode(articleFormVo.getUserCode()));

        Article article = new Article();
        BeanUtils.copyProperties(articleFormVo, article);
        Customer custom = customerService.getCustomByCode(articleFormVo.getUserCode());
        article.setSchoolName(article.getIsAnon()? StringPools.EMPTY : custom.getSchoolName());
        article.setCity(article.getIsAnon()? StringPools.EMPTY : custom.getCity());
        article.setArticleNo(UUIdUtils.getCode());

        articleFormVo.setArticleNo(article.getArticleNo());
        article.setStatus(Enums.Article.STATUS.SUCCESS.name());
        String msg = "文章已发布，回去看看";
        if (!filterWord.isEmpty()) {
            article.setStatus(Enums.Article.STATUS.DOING.name());
            msg = "您的文章内容疑似存在敏感词 [ " + StringUtils.join(filterWord, ",") + "],文章需要审核方可通过!  请同学文明用语哦～";
        }

        handlerImg(articleFormVo);

        handlerVideo(articleFormVo);


        try {
            PUSH_ARTICLE_LOCK.lock(articleFormVo.getUserCode());
            if (!articleFormVo.getIsAnon() && !USER_CODE_ARTICLE.contains(articleFormVo.getUserCode())) {
                integralService.updateIntegral(articleFormVo.getUserCode(), config.getIntegralConfig().getOther(),
                        true, Enums.IntegralWater.SOURCE.PUSH_ARTICLE, null);
                log.error(ServletUtils.getRequest().getSession().getId());
            }
            article.setContent(article.getContent().replaceAll("\n", "<br/>"));

            // 有图片需要人工审核
            if (!articleFormVo.getImages().isEmpty()) {
                article.setStatus(Enums.Article.STATUS.DOING.name());
                msg = "文章已保存，审核中";
            }
            baseMapper.insert(article);
            USER_CODE_ARTICLE.add(articleFormVo.getUserCode());
        } finally {
            PUSH_ARTICLE_LOCK.unlock(articleFormVo.getUserCode());
        }

        return R.success(msg);
    }

    @Transactional
    @Override
    public Article selectByNoForUpdate(String articleNo) {
        return baseMapper.selectByNoForUpdate(articleNo);
    }


    private void uploadImg(ImagesMapper imagesMapper, CyclicBarrier cyclicBarrier, String imgBase64, String articleNo) {
        try {
            // 水印处理
            imgBase64 = ImageUtils.addImageWaterMark(imgBase64, WATER_FILE);
        } catch (IOException e) {
            log.error("水印处理异常", e);
        }
        try {
            Base64Entity base64Entity = new Base64Entity(imgBase64,
                    Enums.Minio.Bucket.article.name().concat("/").concat(UUIdUtils.getCode().concat(StringPools.JPG)));
            base64Entity.setContentType(Enums.Minio.ContentType.IMG.getContentType());

            String upload = FileUploadHandler.getInstant().upload(base64Entity);
            Images image = new Images(Enums.Images.TYPE.ARTICLE.name(), upload, articleNo);
            imagesMapper.insert(image);
            cyclicBarrier.await();
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException("图片处理异常，请重试");
        }
    }

    private void handlerImg(ArticleFormVo articleFormVo) {

        if (Enums.Article.TYPE.valueOf(articleFormVo.getType()) == Enums.Article.TYPE.IMAGETEXT) {
            List<String> images = articleFormVo.getImages();

            if (StringUtils.isNotNull(images) && !images.isEmpty()) {
                if (images.size() > 9) {
                    throw new ServiceException("最多只能上传9张图片");
                }

                ExecutorService executorService = Executors.newFixedThreadPool(images.size());
                final CyclicBarrier cyclicBarrier = new CyclicBarrier(images.size() + Enums.Common.ONE);

                // 图片上传
                images.forEach(img -> {
                    executorService.execute(() -> uploadImg(imagesMapper, cyclicBarrier, img,
                            articleFormVo.getArticleNo()));
                });

                try {
                    cyclicBarrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
                executorService.shutdown();
            }
        }
    }

    private void handlerVideo(ArticleFormVo articleFormVo) {

    }

    @CheckObjectValue(keyValue = @KeyValue(type = Article.class, name = {"articleNo", "userCode", "content"}))
    @Override
    public boolean updateArticleByNo(Article article) {
        return super.update(article, updateLambda().eq(Article::getArticleNo, article.getArticleNo()));
    }

    @Override
    public boolean deleteByNo(String articleNo) {
        return super.remove(updateLambda().eq(Article::getArticleNo, articleNo));
    }

    @Transactional
    @Override
    public boolean agree(@Require String userCode, @Require String articleNo) {
        if (userViewsAgreeMapper.count(userCode, articleNo, Enums.Article.USER_VIEW_TYPE.AGREE.name()) > Enums.Common.ZERO) {
            return true;
        }
        Article article = baseMapper.selectByNoForUpdate(articleNo);
        super.update(article, updateLambda().eq(Article::getArticleNo, articleNo));
        super.update(new Article().setArticleNo(articleNo).setAgreeCount(article.getAgreeCount() + Enums.Common.ONE),
                updateLambda().eq(Article::getArticleNo, articleNo));

        String ownerUserCode = article.getUserCode();
        if (!article.getIsAnon()) {
            messageService.notice(new Message("用户点赞了你的文章:<br/> " + article.getContent(), "文章点赞", ownerUserCode));
        }

        return userViewsAgreeMapper.insert(new UserViewsAgree(userCode, articleNo,
                Enums.Article.USER_VIEW_TYPE.AGREE.name())) > Enums.Common.ZERO;
    }

    @Override
    public boolean view(@Require String userCode, @Require String articleNo) {
        if (userViewsAgreeMapper.count(userCode, articleNo, Enums.Article.USER_VIEW_TYPE.VIEW.name()) > Enums.Common.ZERO) {
            return true;
        }

        Article article = baseMapper.selectByNoForUpdate(articleNo);
        super.update(new Article().setArticleNo(articleNo).setViewCount(article.getViewCount() + 1),
                updateLambda().eq(Article::getArticleNo, articleNo));
        return userViewsAgreeMapper.insert(new UserViewsAgree(userCode, articleNo,
                Enums.Article.USER_VIEW_TYPE.VIEW.name())) > Enums.Common.ZERO;
    }

    @Override
    public Page<ArticleVo> myArticles(Map<String, Object> params, Page<ArticleVo> page) {
        params.put("isAnon", Enums.Common.FALSE_V);
        baseMapper.findList(params, page);
        return page;
    }

    @Override
    public boolean deleteArticleImg(@Require Integer imgId) {
        return imagesMapper.deleteById(imgId) > Enums.Common.ZERO;
    }
}
